home *** CD-ROM | disk | FTP | other *** search
Text File | 1991-03-03 | 78.2 KB | 1,950 lines |
- 1: Introduction
- OASYS stands for Object-Oriented Adventure System. It is a system designed
- for writing text adventure games. The "Object-Oriented" in the title means
- that the system is built around the objects that appear in the game. This
- means that you can write adventure games faster and more easily than with
- older systems.
-
- The system and this manual are designed under the assumption that you know
- the basics of how to use a computer but are not a programmer. From time to
- time you will come across technical notes in the manual, enclosed in
- square brackets [like this]. These contain extra information that you
- don't need to know about to use the system, and you can completely ignore
- them if you like.
-
- [OASYS contains a general-purpose object-oriented programming language
- that could be used to write many different types of programs, however it's
- mostly designed for adventure games.]
-
- 2: Files
- You should have the following files:
-
- OAC The compiler program
- OAD The disassembler program
- OAI The interpreter program
-
- EXAMPLE.S A simple example adventure game
- ESCAPE.S A full-length example adventure game
-
- OASYS.PRN A version of this manual suitable for printing out
- OASYS.DOC A version of this manual suitable for reading on the screen.
-
- You may have the following files which are the source code for OASYS
- itself. You don't need them to use OASYS but if you are a programmer you
- can use them to find out how the system works and to modify it:
-
- INS.H
- OAC.C
- OAD.C
- OAI.C
- RWLIB.H
- RW.LIB
-
- File names have been given in upper case here for clarity. On most
- computers it doesn't matter whether you type file names in upper or lower
- case. However if you are using a UNIX system, you must type all file names
- in lower case.
-
- 3: Usage
- How do you create an adventure game with OASYS? There are two stages in
- the process:
-
- First, you must write the game in the OASYS game description language.
- This can be done with any text editor or word processor. However if you
- are using a word processor, you must make sure that it can write files in
- plain ASCII format - if in doubt, check the manual for your word
- processor.
-
- Suppose you were writing a game called Zork (this is the name of one of
- the most famous adventure games ever written). And suppose you are using
- WordStar to write the game description. You would type:
-
- WS ZORK.S
-
- to create a file called ZORK.S. (OASYS expects the file name to end with
- ".S"). This is called the "source file" or "source code" - hence the S for
- "source" at the end of the file name.
-
- Second, you must translate the human-readable source code into "object
- code" which can be run on the computer. This is done by typing:
-
- OAC ZORK.S
-
- or:
-
- OAC ZORK
-
- (note that it doesn't matter whether or not you type the ".S" at the end
- of the source file name). This will cause the OAC program (the "compiler")
- to translate the human-readable ZORK.S file into the machine-readable ZORK
- file.
-
- Finally, to play the game you type:
-
- OAI ZORK
-
- This will cause the OAI program (the "interpreter") to read the ZORK file
- and run the game. To give your adventure game to other people you must
- give them the ZORK file and the OAI program. You may choose to give them
- the ZORK.S file if you want them to see how your adventure is written. And
- you may choose to give them the OAC file as well if you want them to be
- able to change your adventure (by changing ZORK.S and typing OAC ZORK).
-
- So to summarize, there are two files and two programs involved:
-
- ZORK.S is the human-readable "source file" that you create with a word
- processor.
- ZORK is the machine-readable "object file" that can be played.
- OAC is the program that translates the source file into the object file.
- OAI is the program that runs the object file.
-
- Word Processor -> ZORK.S -> OAC -> ZORK -> OAI -> Player
-
- You can try all this out with the two example adventure games supplied
- with OASYS. To translate and run EXAMPLE, type:
-
- OAC EXAMPLE
- OAI EXAMPLE
-
- Similarly you can translate and run ESCAPE.
-
- What happens if you accidentally type OAI ZORK.S, in other words giving
- OAI the human-readable source file instead of the machine-readable object
- file? It won't do any harm because OAI knows what object files are
- supposed to look like and will reject anything else with an error message.
-
- What about the "disassembler" program mentioned above? You don't actually
- need it at all. It was written to help debug the other two programs, and
- included in the package in case anyone was interested in it. What it does
- is to let you examine the object file. You use it by typing:
-
- OAD ZORK
-
- It will generate a listing of the internal structure of the object file.
- Note that this listing is not in any way a substitute for the original
- source file. Also note that the listing is very long! If you want to try
- out the program, try it out on EXAMPLE first. If you decide to try it on
- ESCAPE, you can press CTRL-C to stop it.
-
- Of course, you will have to translate EXAMPLE.S and ESCAPE.S into EXAMPLE
- and ESCAPE with the OAC program before you can try out OAD.
-
- [Actually you can give the source file any extension you like. However if
- it's anything other than ".S" you must supply the extension when running
- OAC.]
-
- 4: Portability
- Having read the rest of the manual you will know how to write an adventure
- game with OASYS, and having read the previous section you will know how to
- translate and run it. Now what happens when you want to port the game?
- "Port" means "make work on a different type of computer". Suppose you've
- written a game on a PC-compatible machine and you want it to run on a UNIX
- system, you must do the following:
-
- First, get a version of OASYS for the UNIX system. You cannot port OASYS
- itself unless you are a programmer, so if you can't get a version for UNIX
- you'll have to forget about it for the moment.
-
- Now, copy the source file to the UNIX system. Don't try to copy the object
- file! The format it's stored in varies for different types of computers,
- so the UNIX version of OAI won't understand an object file created by the
- PC version of OAC. However it will *think* it can understand it and will
- try to run it, probably crashing the computer.
-
- Now, run the OAC program on the UNIX system to create a version of the
- object file that'll work on UNIX. Then run it with OAI.
-
- Note that only one file - your source file - ever got copied from the PC
- to the UNIX computer.
-
- [Actually it *may* be possible to copy the object file directly. It will
- only work if source and destination machines have the same byte ordering
- and the same int size.]
-
- [If you haven't got a version of OASYS for a target system, you're welcome
- to try porting it yourself - it shouldn't be too hard provided you have a
- C++ compiler for the target system.]
-
- 5: Language
- This section of the manual contains information on how to write your
- source code. It is suggested that you print out EXAMPLE.S and ESCAPE.S and
- refer to them as you read this section of the manual, since a few examples
- are usually better than any amount of explanation. Also you may need to
- scan through the manual several times before you understand all of it.
-
- 5.1: Case
- All examples have been given in upper case for clarity. However it
- doesn't matter whether you type your code in upper or lower case.
- (Unlike filenames, this is true even on UNIX systems). I personally
- prefer lower case and EXAMPLE.S and ESCAPE.S are all in lower case.
-
- 5.2: Spaces
- OASYS regards any amount of "whitespace" characters (space, tab or new
- line) as being the same as a single space. This means that whenever you
- see two words or symbols separated by a space in the example code, you
- could put in any number of spaces or even put them on separate lines.
- Conversely, if you see two things on separate lines, you could put them
- on the same line separated by a space. For the sake of clarity it is
- the convention to put no more than one definition, statement etc. on a
- line but you need not stick to this.
-
- 5.3: Identifiers
- The names of all kinds of things (e.g. object classes, methods etc.)
- must be one word "identifiers". An identifier is a sequence of letters
- and digits but it must begin with a letter. So
-
- ROOM
- SATURN5
- HYDROGENBOMB
-
- are valid identifiers, but
-
- 9TO5
-
- is not (OASYS will look at the 9 at the front and think it's a number,
- then get confused when it sees the TO).
-
- So you can't use more than one word for an identifier - but the
- underscore character "_" is actually counted by OASYS as a letter! So
- the above could be written as
-
- ROOM
- SATURN_5
- HYDROGEN_BOMB
- _9_TO_5
-
- Note that the last is now legal! And the use of "_" makes the names
- more readable. However remember that "_" is NOT the same as the hyphen
- "-".
-
- 5.4: Comments
- Comments may be inserted anywhere at all in your source code. These are
- "marginal notes" to explain how something works, for the benefit of
- other people who want to read your code or for your own benefit if you
- come back in six months time. There are two ways to define comments:
-
- Anything from the symbol // to the end of the line is a comment e.g.
-
- ... // This is a comment
-
- Anything between the symbols /* and */ is a comment e.g.
-
- /* This is
- another comment */
-
- The // comments are better for one-line notes whereas the /*...*/
- comments are better for multi-line explanations.
-
- Another use for comments is "commenting out" code. Suppose you have a
- chunk of code you want to remove but you might want to put it back in
- again later. You don't want to actually delete it because then if you
- do want to put it back in you'll have a lot of unncessary typing to do.
- So you enclose it it comment symbols - then OASYS will ignore it but
- you can put it back in anytime just by deleting the comment symbols.
- Either put // at the beginning of each line or put /* at the beginning
- of the code and */ at the end.
-
- When commenting out code, remember that you can't nest /*...*/ comments
- e.g. suppose you have commented out a method definition as follows:
-
- /*
- METHOD SOMETHING
- {
- ...
- /* This is a comment
- inside the method definition */
- ...
- }
- */
-
- OASYS will think the first */ inside the method definition corresponds
- to the /* before the method definition. So it will only see the second
- half of the method definition, and an apparently unmatched */ symbol!
- Where you have /*...*/ comments inside something you want to comment
- out, you should do the commenting out with // symbols.
-
- 5.5: Order of definitions
- When writing the various sections of your source code, remember that
- OAC only scans through the source file *once* for the sake of speed.
- This means that everything defined in one part of the source code and
- referred to in another part must be defined *before* it is referred to.
-
- 5.6: Objects
- OASYS is based on objects. Unlike other systems, even locations and the
- player are objects. When a game is starting, no objects exist. You must
- arrange for some objects (such as the player and locations!) to be
- created at the start. Later more objects may be created and old ones
- destroyed. Every object belongs to a class, so the first thing to do is
- usually to define what classes you are going to have. (Remember
- everything must be defined before it's referred to and you're going to
- refer to your classes a lot!) For example:
-
- CLASS PLAYER {{ME} {SELF} {MYSELF}}
- CLASS ROOM {}
- CLASS MACHINE_GUN {{GUN} {MACHINE GUN}}
-
- The first of these is the class to which the player object is going to
- belong. The things after the word PLAYER mean that the (human) player
- may refer to himself with the words ME, SELF or MYSELF e.g. EXAMINE
- MYSELF or KILL SELF. The empty pair of curly brackets after the word
- ROOM means that the player cannot refer to objects of class ROOM. The
- definition of CLASS MACHINE_GUN says that the player can refer to
- objects of this class with the word GUN or the phrase MACHINE GUN, so
- GET GUN or GET MACHINE GUN will mean the same thing.
-
- In general, a class definition consists of the word CLASS followed by
- the class name, an open curly bracket "{", a list of phrases which can
- be used to refer to objects of that class, and a close curly bracket
- "}". Each phrase is an open curly bracket, a list of words and a close
- curly bracket. The reason you need the curly brackets around the list
- of words is to tell OASYS when the list of words is finished, and the
- reason you need the curly brackets around the list of phrases is to
- tell OASYS when the list of phrases is finished.
-
- Note that unlike many systems, OASYS lets you have many objects
- belonging to the same class, so you can have say several identical
- MACHINE_GUNs. Of course all your locations will be objects of class
- ROOM. (Unless you decide for some reason to have some locations of say
- class TUNNEL or TREETOP or whatever).
-
- 5.7: Properties
- As well as belonging to classes objects have properties, such as
- description and weight. Unlike other systems OASYS starts with no
- preconceived idea of what properties objects are going to have. You
- must define the list of properties. When you have done this each object
- in your game will possess different values for these properties, for
- example:
-
- PROPERTY INT WEIGHT
- PROPERTY STRING DESCRIPTION
- PROPERTY OBJECT IN
-
- As you can see, each property in the list starts with the word
- PROPERTY. Then is the property type, which can be INT, STRING or
- OBJECT. Last comes the name of the property.
-
- INT properties are a number, in this example WEIGHT. These properties
- are called INT because the number must be an integer i.e. a whole
- number. (Numbers with decimals may be allowed in a future version of
- OASYS).
-
- STRING properties are a piece of text. The name STRING comes from the
- fact that each is a "string" of letters, digits, punctuation marks or
- whatever.
-
- OBJECT properties are a reference to another object.
-
- So how would the above be used? All objects of class MACHINE_GUN might
- be assigned a WEIGHT of 15 when created; objects of other classes might
- be assigned different WEIGHTs. (Of course you could also assign
- different WEIGHTs to different MACHINE_GUNs).
-
- WEIGHT is not useful for objects of class ROOM. All objects will have a
- WEIGHT of 0 when created, so you would not bother to change this for
- ROOMs.
-
- DESCRIPTION would be used for almost all types of objects. (An
- exception might be the player). For example, one ROOM might have a
- DESCRIPTION of "Long dark tunnel" whereas another ROOM might have a
- DESCRIPTION of "Large hall". All MACHINE_GUNs might have a DESCRIPTION
- of "A machine gun".
-
- All STRING properties have an initial value of "*NULL STRING*" so that
- if you forget to assign the proper value to one you will notice quickly
- when testing the game.
-
- IN in this example is used to indicate what other object this object is
- contained in. For example, the player's IN value might indicate which
- ROOM he is in. An object's IN value might refer to a ROOM object. Or it
- might refer to the player object to mean that the object was being
- carried by the player.
-
- All OBJECT properties start off with a value of OBJECT 0 which means no
- object at all. (The exact meaning of the expression OBJECT 0 is
- explained in the "Expressions" section of the manual). You would leave
- a ROOM's IN property as OBJECT 0 because ROOMs are not IN anything.
-
- 5.8: Methods
- Methods are how everything gets done in OASYS. For example:
-
- METHOD INT SQUARE INT X
- {
- RETURN X * X
- }
-
- is a method which will calculate the square of a number. The definition
- consists of:
-
- the word METHOD
-
- the name of the method, SQUARE
-
- the word INT to indicate that it returns an INT value. (Types that
- methods can return are INT, STRING and OBJECT, the same as for
- properties).
-
- one "argument" passed to the method. This method is called X and is of
- type INT. (Types for method arguments are INT, STRING and OBJECT).
-
- an open curly bracket to indicate the start of the actual method code.
-
- the method code, in this case just one line to cause the square of the
- argument X to be returned.
-
- a close curly bracket to indicate the end of the method definition.
-
- Now here is another method:
-
- METHOD LOOK VERBS {{LOOK} {DESCRIBE LOCATION}}
- {
- PRINT PLAYER IN DESCRIPTION
- }
-
- This consists of:
-
- the word METHOD
-
- *no* type name for the method - this is because this method does not
- return anything at all.
-
- the name of the method, LOOK
-
- the word VERBS
-
- a list of phrases which the player can type to call this method - this
- is the same as the list of nouns for a CLASS.
-
- the method code as above - this time the method code prints the
- description of the ROOM the player is IN, but does not RETURN anything.
-
- So some methods are actually verbs. Whenever the (human) player types a
- command, OASYS searches for a method with a corresponding verb. If
- found, the method is called. The method SQUARE had no VERBS and so was
- purely for internal use.
-
- A method with VERBS can't have any STRING arguments. (This limitation
- may be removed in future versions). It can have INT arguments, but the
- player is not allowed to type negative numbers. (This limitation may
- also be removed in future versions). However if the method is also
- called by other methods, the other methods can supply negative numbers
- for the INT arguments even though the player can't.
-
- Suppose you want a method with both verbs and arguments? You can
- specify the position of the arguments in the verbs e.g.
-
- METHOD GIVE_TO OBJECT X IS_CARRIED OBJECT Y IS_VISIBLE
- VERBS {{GIVE X TO Y} {OFFER X TO Y} {GIVE Y X} {OFFER Y X}}
- {
- ...
- }
-
- What this means is that if the player wants to give something to
- something else he must type the "give" command with the names of the
- two objects. The first object, which is being given, must be carried by
- the player. The second object, which is being given the first object,
- must be visible. (For further explanation of IS_CARRIED and IS_VISIBLE,
- see below). The alternative ways the player can phrase the command are
- given in the VERBS definition. There is an example in ESCAPE.S where
- the player must give a fish to a tiger, and the alternative ways this
- could be phrased are:
-
- GIVE FISH TO TIGER
- OFFER FISH TO TIGER
- GIVE THE TIGER THE FISH
- OFFER THE TIGER THE FISH
-
- The word THE, inserted above for clarity, is always ignored when the
- player types in a command so that is why it is not present in the VERBS
- definition. You can use more informative names than X and Y for the
- method arguments but whatever names you use must be correctly placed in
- the VERBS definition e.g.
-
- METHOD GIVE_TO OBJECT GIFT IS_CARRIED OBJECT RECIPIENT IS_VISIBLE
- VERBS {{GIVE GIFT TO RECIPIENT} {OFFER GIFT TO RECIPIENT}
- {GIVE RECIPIENT GIFT} {OFFER RECIPIENT GIFT}}
- {
- ...
- }
-
- would also work.
-
- 5.9: Variables
- As well as properties you can have "global variables" which are like
- properties but there's only one of them. For example,
-
- INT SCORE
-
- could record the current score in the game. You only want this recorded
- once not for every object, so you make it a global variable rather than
- a property. This is done by only having the type (INT, STRING or
- OBJECT) and the name, without the word PROPERTY.
-
- Also you can have what are called "local variables" within methods. You
- define them exactly like global variables, but put the definition
- *inside* the method definition, just before the method code. e.g.
- METHOD SQUARE could be rewritten:
-
- METHOD INT SQUARE INT X
- {
- INT RESULT
-
- RESULT = X * X
- RETURN RESULT
- }
-
- The variable RESULT only exists within METHOD SQUARE. You could define
- STRING RESULT in another method elsewhere in the source code and there
- would be no conflict.
-
- Note that it is the convention to separate the variable definition(s)
- from the method code with one blank line (but this is not required).
-
- Why use local variables at all when global variables will do the same
- job? Well, if you only want to use a variable within one method, making
- it a local variable makes it a lot easier to keep track of what you're
- doing because if you forget where you used it you have a lot less code
- to search through. Also they're essential for "recursion" (q.v.).
-
- You can't have two global variables with the same name, or two local
- variables with the same name within the same method. What if you have a
- global variable, and within a method a local variable with the same
- name? e.g. suppose in the above example there was a global variable
- OBJECT RESULT already defined? No problem. Within METHOD SQUARE, RESULT
- would be taken to mean the local INT RESULT. Within other methods,
- RESULT would be taken to mean the global OBJECT RESULT.
-
- Arguments to methods have the same status as local variables. For
- example, in METHOD SQUARE it would not have been legal to have a local
- variable STRING X because it would conflict with the argument INT X.
-
- 5.10: THIS
- All methods are considered to be "applied to" an object, for example
- methods that correspond to typed commands are considered to be applied
- to the PLAYER object. The way this works is that all methods have an
- implicit argument OBJECT THIS which is whatever the method is applied
- to.
-
- So suppose you have a METHOD GET which is called when the player wants
- to GET an object. THIS will then be the player object. On the other
- hand if the player tells something else to GET something (see below for
- how to do this), THIS will be that something else. So you can write the
- method to distinguish between when THIS is the player (in which case
- the command will be carried out) and when THIS is not the player (in
- which case there may be a refusal to carry out the command!). For
- another example of the use of THIS, see "selector methods".
-
- On the other hand for methods like SQUARE above which give the same
- result no matter what object they're applied to, THIS won't be used at
- all, although it must still be supplied.
-
- Whenever a method is called applied to OBJECT 0, it will return
- immediately without executing any of the method code. The return value
- if any will be set to 0 for an INT method, "*NULL STRING*" for a STRING
- method or OBJECT 0 for an OBJECT method.
-
- 5.11: METHOD INIT
- Methods with verbs are called when the player types commands, and this
- is how things get done. However when the game starts some objects need
- to be created initially and other setting up may also need to be done.
- The way this is done is, you must have a method defined as follows:
-
- METHOD INIT
- {
- ...
- }
-
- i.e. it must not return a value or have arguments or verbs (though it
- may have local variables). This method will get called at the start of
- every game.
-
- 5.12: Selector methods
- Consider this definition (used in the section on "Methods"):
-
- METHOD GIVE_TO OBJECT X IS_CARRIED OBJECT Y IS_VISIBLE
- VERBS {{GIVE X TO Y} {OFFER X TO Y} {GIVE Y X} {OFFER Y X}}
- {
- ...
- }
-
- The IS_CARRIED and IS_VISIBLE are called "selector methods". The point
- is, suppose you have many MACHINE_GUNs in the game and the player types
- GIVE MACHINE GUN TO GUARD, and the player is only carrying one machine
- gun, obviously you want your GIVE_TO method to be called with the
- correct machine gun, otherwise the GIVE_TO method will only see that
- the player does not have X, it will not see that there is another
- machine gun that he does have, so it will idiotically respond that the
- player does not have the machine gun.
-
- Also in traditional adventure writing systems, the writer has to waste
- a great deal of time checking, for each and every possible command,
- that the player has or can see the objects referred to. So you want the
- checking to be done *before* your GIVE_TO or whatever method is called.
-
- Selector methods are the answer to this. The above tells OASYS, "for
- argument X, call method IS_CARRIED to check whether it's valid; for
- argument Y, call method IS_VISIBLE to check whether it's valid". This
- will produce the desired result if you have previously defined
- something like:
-
- METHOD INT IS_CARRIED
- "You haven't got that.\n"
- {
- RETURN THIS IN == PLAYER
- }
-
- METHOD INT IS_VISIBLE
- "That isn't here.\n"
- {
- RETURN THIS IS_CARRIED OR THIS IN == PLAYER IN
- }
-
- Of course you can have as many selector methods as you like and call
- them whatever you like but the above two are widely useful.
-
- More complicated versions of IS_CARRIED and IS_VISIBLE are defined in
- ESCAPE.S to cope with the possibility that for example, something might
- still be visible if it was in an object that itself was in the player's
- location.
-
- Selector methods take no arguments. THIS represents the object which is
- being tested. The selector method must return an INT value, which is
- zero if THIS is not valid and non-zero if THIS is valid.
-
- The two messages just before the open curly brackets for the two
- methods are the messages that will be displayed if the object is found
- not to be carried or visible respectively. (Such messages should only
- be defined for selector methods, not for other methods).
-
- Why can the message not simply be displayed by the selector method
- itself if THIS is found to be invalid, instead of having to be
- specially defined for OASYS to display? Because suppose the player
- types GIVE MACHINE GUN TO GUARD and you have many machine guns in the
- game, none of which is in the player's possession. Each and every one
- of them will be checked for IS_CARRIED but you only want one "You
- haven't got that" message to be displayed.
-
- Of course if you know that you only have one object of each class in
- your game, you can have the message printed by the selector method
- instead of defining it before the open curly bracket (say if you want
- to give a more specific message like "You haven't got the machine
- gun.").
-
- 5.13: METHOD SELECT_ADDRESSEE
- The player can talk to other objects in the game! The command for this
- is for example "GUARD, DROP MACHINE GUN" will give the command to drop
- the machine gun to the guard as typing "DROP MACHINE GUN" would give
- the command to drop the machine gun to the player object. Of course you
- would probably have programmed your METHOD DROP to check if the DROP
- command was being given to something other than the player, and if so
- not necessarily carry out the order!
-
- In general, to give a command to another object, the player must type
- the name of the object to be addressed, followed by a comma and then
- the rest of the command.
-
- The problem arises again here of how to select which objects the player
- can talk to, just as it did earlier of how to select which objects a
- command can apply to. For example, if you have many guards in the game
- you want to make sure the one the player ends up talking to is the one
- in the same location as the player.
-
- There is a special selector method for things the player can talk to.
- It's called METHOD SELECT_ADDRESSEE. If you have this defined it will
- be used, if you haven't got it defined the player will just get the
- message "You can't talk to that" whenever he tries to talk to anything.
- An example is:
-
- METHOD SELECT_ADDRESSEE
- "It doesn't understand you!\n"
- {
- RETURN (THIS IS GUARD OR THIS IS ROBOT) AND THIS IS_VISIBLE
- }
-
- which will only let you talk to GUARDs or ROBOTs which are visible. (Of
- course, you must have previously defined the selector method
- IS_VISIBLE).
-
- If you don't have the message defined for METHOD SELECT_ADDRESSEE it
- will default to "You can't talk to that".
-
- 5.14: Player
- The player is represented by an object. There is a predefined global
- variable OBJECT PLAYER which tells OASYS which is the player object.
- Suppose you have defined
-
- CLASS PLAYER {{ME} {SELF} {MYSELF}}
-
- (which will let the player type commands like EXAMINE SELF and KILL
- MYSELF), you must put into METHOD INIT something along the lines of:
-
- PLAYER = CREATE PLAYER
-
- Of course, you don't have to have a CLASS PLAYER defined; if you don't
- want the player to be able to type commands that refer to himself and
- you have already defined
-
- CLASS ROOM {}
-
- for locations, you could have
-
- PLAYER = CREATE ROOM
-
- in METHOD INIT, and it would work perfectly well.
-
- This means that you can have a facility in your game whereby the player
- can take on the identity of many different characters at different
- times. For example in the game "Lord of the Rings" on the Commodore 64,
- the four hobbits Frodo, Sam, Merry and Pippin appeared as characters
- and the player could play any of them with the BECOME command.
- Supposing the player started off playing FRODO, he could type BECOME
- SAM. This could be implemented in OASYS like this:
-
- CLASS FRODO {{FRODO}}
- CLASS SAM {{SAM}}
- CLASS MERRY {{MERRY}}
- CLASS PIPPIN {{PIPPIN}}
-
- ...
-
- OBJECT FRODO
- OBJECT SAM
- OBJECT MERRY
- OBJECT PIPPIN
-
- ...
-
- METHOD INT IS_BECOMABLE
- {
- RETURN THIS SELECT_ADDRESSEE AND (THIS IS FRODO OR
- THIS IS SAM OR
- THIS IS MERRY OR
- THIS IS PIPPIN)
- }
-
- METHOD BECOME OBJECT X IS_BECOMABLE VERBS {{BECOME X}}
- {
- PLAYER = X
- }
-
- METHOD INIT
- {
- ...
- FRODO = CREATE FRODO
- SAM = CREATE SAM
- MERRY = CREATE MERRY
- PIPPIN = CREATE PIPPIN
-
- PLAYER = FRODO // Player starts off playing Frodo
- ...
- }
-
- Note that if you have already defined METHOD SELECT_ADDRESSEE you can
- use it to help select which objects are becomable. The above example
- assumes that there are some characters in the game which the players
- can talk to but not become.
-
- Also you must have different CLASSes for each of the four hobbits
- rather than for example just a CLASS HOBBIT because the player has to
- be able to distinguish between the four when typing commands.
-
- 5.15: Parser
- The "parser" is that part of the OAI program which accepts typed input
- from the player and calls methods accordingly.
-
- 5.15.1: Features
- The player may talk to other objects by prefacing the command with
- the name of the other object and a comma.
-
- The word THE is ignored, so TAKE MACHINE GUN is the same as TAKE THE
- MACHINE GUN (and for that matter the same as THE THE TAKE THE
- MACHINE THE GUN).
-
- Verbs and nouns may have multiple words e.g. PICK UP THE MACHINE GUN
- is as easy to implement as TAKE THE AXE. However each alternative
- form of the noun must be defined in the CLASS definition and each
- alternative form of the verb must be defined in the METHOD
- definition.
-
- Words in the verb may be interspersed with nouns e.g. PICK THE
- MACHINE GUN UP as well as PICK UP THE MACHINE GUN can be
- implemented. Again this must be specified in the METHOD definition
- with something like VERBS {{GET X} {TAKE X} {PICK UP X} {PICK X
- UP}}.
-
- The player may type commands in upper case, lower case or a mixture
- of both, it doesn't matter which.
-
- 5.15.2: Process
- The process of interpreting a command works as follows:
-
- Each word is checked to make sure it appears somewhere in a CLASS or
- METHOD definition. If a completely unrecognized word is found, the
- player will receive the message "I don't understand the word 'xxx'."
- where xxx is the offending word. Due to a quirk of the OAC program
- design, the names of method arguments are regarded as valid words
- even if they are not included in any verb or noun definitions. (This
- may not be true in later versions of OASYS). So incorrect use of
- such names will give the player the message "I don't understand
- you." rather than the more specific message above.
-
- If there is a comma, the noun before the comma is compared with the
- nouns given in CLASS definitions to decide what CLASS the addressee
- belongs to. If no such class can be found, the player receives the
- message "I don't know who you're trying to talk to." If METHOD
- SELECT_ADDRESSEE is not defined, the player receives the message
- "You can't talk to that.". Otherwise METHOD SELECT_ADDRESSEE is used
- to find which actual object of that class is being addressed. If no
- such object can be found, the player receives the message defined in
- METHOD SELECT_ADDRESSEE, or "You can't talk to that." if there is no
- such message defined.
-
- The command as a whole is compared with the verbs for the various
- methods to decide which method is involved. If no method can be
- found with a corresponding verb, the player will receive the message
- "I don't understand you.".
-
- Each noun is compared with the nouns given in CLASS definitions to
- decide which CLASS the noun refers to. Then the selector methods are
- used to decide which actual object is being referred to for each
- noun. If no object can be found, the player receives the message
- defined in the selector method, if any. In either case the player
- must type another command.
-
- The method with the corresponding verb is called with the
- appropriate arguments. THIS is set to PLAYER if the command was
- typed by itself, or the appropriate object if the command had an
- addressee.
-
- 5.15.3: Omissions
- The following features which are found in the parsers in some other
- systems are not included in the OASYS parser, although they may be
- included in future versions:
-
- The player must type only one command at a time, he cannot type
- several commands separated by the word THEN.
-
- The word IT cannot be used to refer to a noun in the previous
- command.
-
- The player cannot refer to many objects at once with the words AND
- or ALL.
-
- Adjectives as distinct from nouns cannot be used e.g. if you have a
- PLASTIC KEY, a METAL KEY and a CERAMIC key you must define the nouns
- as PLASTIC KEY, METAL KEY and CERAMIC KEY and the player must type
- the noun in full. He cannot just type KEY in the hope that it will
- be obvious which key is being referred to.
-
- 5.16: Code for methods
- 5.16.1: Statements
- The code for a method consists of a sequence of statements. A
- statement may be one of the following:
-
- 5.16.1.1: { statements }
- A sequence of zero or more statements surrounded by curly
- brackets is equivalent to a single statement. This is used for
- IF, WHILE and DO...WHILE constructs - if you want more than a
- single statement in one of these you must surround them with
- curly brackets. The conventional layout for writing statements
- inside curly brackets is:
-
- {
- statement 1
- statement 2
- ...
- }
-
- i.e. put the "{" and "}" on separate lines and indent the
- statements inside by one tab space.
-
- 5.16.1.2: PRINT expression
- The word PRINT followed by an expression will display the
- expression on the screen. The expression may be an integer or
- string value. The print routine automatically arranges text for
- output so that words are not broken up at the ends of lines. It
- doesn't output anything until it has collected a whole word e.g.
-
- PRINT "F"
- PRINT "R"
- PRINT "E"
- PRINT "D "
-
- will cause the word FRED to appear on the screen, but nothing
- will actually appear until the last PRINT statement with the
- space at the end is reached. A space or a new line character "\n"
- will cause the collected output to be displayed.
-
- 5.16.1.3: RETURN
- In methods which don't return a value, the word RETURN by itself
- will cause execution of the method to finish. It's normally used
- only with an IF statement because a return will happen
- automatically if execution reaches the end of the method so you
- don't need to use the word RETURN.
-
- 5.16.1.4: RETURN expression
- In methods which do return a value, the word RETURN must be
- followed by an expression of the appropriate type. The value of
- the expression will be returned. If execution reaches the end of
- the method without encountering a RETURN statement, a default
- value will be returned following the same rules as for initial
- values of variables i.e. INT methods will return 0, STRING
- methods will return "*NULL STRING*" and OBJECT methods will
- return OBJECT 0.
-
- 5.16.1.5: DESTROY expression
- This will destroy the object given by the expression. If the
- value of the expression is OBJECT 0 then OAI will stop with an
- error message.
-
- When an object is destroyed, OASYS will look at all global
- variables and object properties. Any which have OBJECT type and
- refer to the destroyed object will be set to object 0. This does
- *not* apply to local variables.
-
- So suppose you have a global variable OBJECT DYNAMITE which
- refers to some dynamite. When the dynamite gets used up, you will
- want to destroy the dynamite object to save memory, and also to
- set the global variable DYNAMITE to OBJECT 0 to record the fact
- that the dynamite isn't there anymore. You would expect to have
- to do:
-
- DESTROY DYNAMITE
- DYNAMITE = OBJECT 0
-
- but the second part is done automatically by OASYS so you just
- need:
-
- DESTROY DYNAMITE
-
- [There is an exact correspondence between OASYS and C as follows:
-
- OBJECT Pointer
- CREATE (q.v.) malloc()
- DESTROY free()
- OBJECT 0 Null pointer
-
- *except* for the additional behaviour of DESTROY mentioned
- above.]
-
- 5.16.1.6: EXIT
- The EXIT statement will cause the game to end immediately. The
- player will be asked if he wants to play again and if he does, a
- new game will start.
-
- 5.16.1.7: QUIT
- The QUIT statement will cause an "Are you sure? (Y/N)" message to
- be displayed on the screen and the player will have to type Y, in
- which case the game will stop as for the EXIT statement, or N, in
- which case the game will continue as if nothing had happened.
- QUIT should be used when the player has typed a QUIT command,
- whereas EXIT should be used when the player has been killed or
- for some other reason the player has no choice about the game
- being over.
-
- 5.16.1.8: SAVE
- The SAVE statement will cause the current state of the game -
- global variables and the list of objects - to be saved to disk.
- (The player will be prompted for a file name). After the save has
- been done the game will continue as normal.
-
- 5.16.1.9: expression = expression
- The expression on the left hand side of the = sign must be the
- name of a global variable, local variable, method argument or a
- property of some object. The expression on the right hand side
- may be any expression of the same type as the one on the left
- hand side. The variable or whatever on the left hand side will be
- assigned the value on the right hand side as its new value, e.g.
-
- INT FRED
-
- FRED = 5
-
- 5.16.1.10: IF expression statement [ELSE statement]
- The expression must be of type INT. The statement will be
- executed if and only if the expression has a nonzero value. The
- ELSE part, if present, will be executed if and only if the
- expression has a zero value e.g.
-
- INT FLAG
-
- ...
-
- FLAG = 0
-
- ...
-
- IF FLAG PRINT "NOT ZERO\n" ELSE PRINT "ZERO\n"
-
- will cause ZERO to be printed.
-
- 5.16.1.11: WHILE expression statement
- The expression must be of type INT. The statement will be
- executed repeatedly as long as the expression has a nonzero value
- e.g.
-
- INT X
-
- X = 5
- WHILE X
- {
- PRINT "X = "
- PRINT X
- PRINT "\n"
- X = X - 1
- }
-
- will give the output:
-
- X = 5
- X = 4
- X = 3
- X = 2
- X = 1
-
- Care must be taken to ensure that the loop will eventually
- terminate. For example, if the statement X = X - 1 had been
- omitted in the above example, the code would have kept printing X
- = 5 forever. OAI has no way to detect such a condition so the
- player would have to stop the program by pressing CTRL-C.
-
- 5.16.1.12: DO statement WHILE expression
- The DO...WHILE loop is like a WHILE loop except that the
- statement is executed before the expression is tested, so it is
- always executed once regardless of the value of the expression.
-
- 5.16.1.13: BREAK
- A BREAK statement will jump out of the enclosing WHILE or
- DO...WHILE loop. If the BREAK statement is not in a loop, an
- error message will be given.
-
- 5.16.1.14: CONTINUE
- A CONTINUE statement will jump to just before the end of the
- enclosing WHILE or DO...WHILE loop. If the CONTINUE statement is
- not in a loop, an error message will be given.
-
- 5.16.1.15: expression method_name arguments
- A call to a method which does not return a value is a statement.
- The expression must be of type OBJECT, and gives the object to
- which the method will be applied. This is followed by the name of
- the method and then by a list of expressions, one for each of the
- method's arguments if any, e.g.
-
- PLAYER IN DESCRIBE "You are in "
-
- assuming a previous definition of:
-
- METHOD DESCRIBE STRING MSG
- {
- PRINT MSG
- PRINT THIS DESCRIPTION
- PRINT ".\n"
- }
-
- Methods which return a value cannot be called in this way - their
- return value must be used. If you don't want to do anything with
- the return value, the best thing to do is to assign it to some
- variable.
-
- 5.16.2: Expressions
- Expressions are used in statements. (An attempt to use an expression
- on its own in place of a statement will usually result in the error
- message "Statement expected"). An expression may be one of the
- following:
-
- 5.16.2.1: integer
- An ordinary integer number is an expression of type INT, e.g. 42,
- 5, 367. Commas and decimals points are not allowed i.e. 1000 must
- be written as 1000 not 1,000 or 1000.0. Negative numbers are
- allowed e.g. -65, but these are treated as expressions consisting
- of a minus sign followed by a positive number (see below).
-
- There is a limit to how high or low a value numbers in your
- adventure can have. It depends on the type of computer system you
- have, and also possibly on the version of OASYS you have. Some
- types of computer can handle "32-bit" numbers which can range
- from -2147483648 to 2147483647 i.e. about minus two billion to
- plus two billion, which should be plenty for most purposes! Other
- types of computer can handle only "16-bit" numbers which can
- range from -32768 to 32767 i.e. minus thirty-two thousand to plus
- thirty-two thousand, which should still be enough in most cases.
-
- If it matters then you can find out whether or not your computer
- can handle 32-bit numbers:
-
- A guideline is that versions of OASYS on IBM PC-compatible
- computers can probably only handle 16-bit numbers whereas those
- on other types of system can probably handle 32-bit numbers.
-
- You can find out for sure by copying EXAMPLE.S and putting
- something like "PRINT 1000000" into the INIT method. If you get
- the right answer i.e. 1000000 on the screen, you can use 32-bit
- numbers, if you get the wrong answer then you can only use 16-bit
- numbers.
-
- While it is possible for different versions of OASYS to differ in
- their ability to handle 32-bit numbers, if your copy of OASYS can
- handle 32-bit numbers, it will always be able to do so on any
- computer on which it will work at all. This is another reason to
- not necessarily throw away your old version of OASYS if you get a
- new version (see the section on "Versions").
-
- 5.16.2.2: string
- A string is anything between pairs of double quote marks. The
- quote marks are not counted as part of the string, e.g. if you
- have PRINT "HELLO WORLD!" in a method, the following will come up
- on the screen when the game is run:
-
- HELLO WORLD!
-
- What if you have a string longer than will fit on one line of
- your source code e.g. a location description that extends over
- several lines? There are two ways to do this.
-
- First you can just continue the string on the next line e.g.
-
- PRINT "You are in a
- large cavern."
-
- The line break between "a" and "large" will be regarded as a
- space so the display of the string on the player's screen won't
- be messed up no matter how messy it looks in your source code.
-
- Unfortunately even if you have the first line indented as the
- PRINT statement above is, you must start subsequent lines at the
- beginning of the line, otherwise the indentation will be regarded
- as part of the string e.g.
-
- PRINT "You are in a
- large cavern."
-
- looks nicer in the source code, but will produce
-
- You are in a large cavern.
-
- on the player's screen.
-
- You must also be careful not to have any spaces after the "a" on
- the PRINT statement line, because these are not visible in your
- source code but will also appear on the player's screen.
-
- Second, you can stop the string and start it again. As long as
- you have nothing else other than spaces, tabs and line breaks
- between the close quotes and the next open quotes the whole thing
- is regarded as one string e.g.
-
- PRINT "You are in a "
- "large "
- "cavern."
-
- Here only the stuff inside the quotes is regarded as part of the
- string so you can indent the subsequent parts, so your source
- code looks nicer, so this is the method used in ESCAPE.S.
-
- However this time you do have to include the spaces between "a"
- and "large" and between "large" and "cavern" in the quotes.
-
- The PRINT statement will cause output to go onto the next line
- automatically when it reaches the end of the current line so you
- don't have to worry much about going onto new lines. However you
- *must* force the output to go onto a new line before the player
- is asked for the next command otherwise the player will get
- output like this:
-
- You are in a large cavern.>*
-
- where the player must type in his next command at the * in the
- middle of the line. You can avoid this and force the output to go
- onto a new line like this:
-
- PRINT "You are in a large cavern."
- PRINT "\n"
-
- or like this:
-
- PRINT "You are in a large cavern.\n"
-
- i.e. a "\" (backslash) character followed by the letter N (upper
- or lower case) will force the output to go onto a new line.
-
- Also a "\" character followed by double quotes will cause the
- double quotes to be put into the string rather than interpreted
- as the end of the string e.g.
-
- PRINT "The giant says \"Fee fie fo fum!\"\n"
-
- will produce:
-
- The giant says "Fee fie fo fum"!
-
- Of course you can avoid the whole problem by just using single
- quote marks inside strings e.g.
-
- PRINT "The giant says 'Fee fie fo fum!'\n"
-
- will produce
-
- The giant says 'Fee fie fo fum!'
-
- which works just as well.
-
- [You can put any chacter whatsoever into a string by placing a
- "\" followed by the ASCII code for the character as a two-digit
- hexadecimal number e.g. \07 will put a beep into the string. This
- method must be used to get an actual "\".]
-
- 5.16.2.3: expression operator expression
- The operator may be either an arithmetic, comparison or logical
- operator.
-
- 5.16.2.3.1: Arithmetic operators
- The arithmetic operators are:
-
- + addition
- - subtraction
- * multiplication
- / division
- % remainder after division
-
- All take two INT expressions and return an INT expression e.g.
-
- 4 + 5
-
- gives 9,
-
- 29 / 10
-
- gives 2 (in OASYS, division always rounds down) and
-
- 29 % 10
-
- gives 9 (the remainder when 29 is divided by 10).
-
- 5.16.2.3.2: Comparison operators
- The comparison operators compare two things and return a true
- or false result accordingly. For example
-
- A > B
-
- returns true if A is greater than B and false otherwise. There
- is no actual type for true or false in OASYS so integers are
- used instead. A zero integer means false, a nonzero integer
- means true. So
-
- PRINT A > B
-
- will display 1 if A is greater than B and 0 otherwise, and
-
- DO
- {
- ...
- }
- WHILE 1
-
- is a standard way to keep repeating something forever (or
- until a BREAK statement or something similar is executed
- within the loop).
-
- == Equals
- != Not equals
- > Greater than
- < Less than
- >= Greater than or equal to
- <= Less than or equal to
-
- The first two can be used to compare objects and strings as
- well as integers i.e. you can test whether two strings or two
- objects are the same or not. The last four can only be used on
- integers i.e. it doesn't make sense to try to check whether
- one object or string is greater than or less than another.
-
- Why the double equals sign "==" for the operator to test for
- equality? To distinguish it from the assignment "=" sign. It
- wasn't practical to have them both the same symbol so
- something else had to be used for one or other of them. It was
- decided to reserve the one-character symbol "=" for assignment
- to save typing on the grounds that assignment is done more
- often than testing for equality.
-
- Two expressions of type OBJECT are regarded as equal only if
- they actually refer to the *same* object, not just if they
- refer to objects of the same class or with the same values for
- all properties.
-
- Similarly, two strings are regarded as equal only if they are
- actually the *same* string, not just if they look identical!
- e.g.
-
- "FRED" == "FRED"
-
- returns 0! Whereas given
-
- STRING S1
- STRING S2
-
- S1 = "FRED"
- S2 = S1
-
- then
-
- S1 == S2
-
- returns 1. (In future versions of OASYS, the result may be 1
- in both cases). For further explanation, see the section on
- "Efficiency".
-
- "A != B" is of course the same as "NOT (A == B)", similarly
- ">" is the negation of "<=" and "<" is the negation of ">=".
-
- 5.16.2.3.3: Logical operators
- The logical operators in OASYS are OR and AND (also see NOT
- below). They take true or false values (such as those returned
- by the comparison operators) and give true or false values
- accordingly e.g.
-
- 1 == 2 OR 1 == 1
-
- returns 1 whereas
-
- 1 == 2 AND 1 == 1
-
- returns 0 because OR will return 1 if either the expression on
- the left OR the expression on the right (or both) is true,
- whereas AND will return 1 only if both the expression on the
- left AND the one on the right are true.
-
- In the second case, the AND operator could have known it was
- going to return 0 as soon as it evaluated the expression on
- the left. Hence there was no need for it to evaluate the
- expression on the right. However the OASYS AND and OR
- operators are not very intelligent and always evaluate both
- expressions even when there is no need. This can be important
- when one expression has "side-effects" i.e. causes things to
- happen as well as returning a value, such as a method call.
- See "Recursion" for an example.
-
- 5.16.2.3.4: Operator precedence
- In OASYS just as in mathematics some operators are calculated
- before others e.g. 1 + 2 * 3 means 1 + (2 * 3) not (1 + 2) *
- 3. The list of operators is shown below in descending order of
- precedence:
-
- * / %
- + -
- > < >= <=
- == !=
- AND
- OR
-
- The order is designed so that expressions usually work the way
- you expect them to e.g.
-
- A == B AND C == D
-
- means
-
- (A == B) AND (C == D)
-
- rather than
-
- A == (B AND C) == D
-
- as would be the case if AND had a higher precedence than "==".
- While legal, the above is probably not what was intended!
-
- The unary minus sign and NOT (q.v.) both have higher
- precedence than any normal operator so -5 * 7 means (-5) * 7
- rather than -(5 * 7).
-
- 5.16.2.4: - expression
- A minus sign followed by a number gives the negative of the
- number. This is a special case called "unary minus" and is
- regarded as quite distinct from the use of the minus sign to
- denote subtraction.
-
- The plus sign cannot be used in the same way i.e. +5 on its own
- does not mean the same thing as 5, instead it generates an error.
-
- 5.16.2.5: NOT expression
- The word NOT followed by an expression gives zero if the
- expression is nonzero and 1 if the expression is zero, e.g.
-
- NOT 4 == 5
-
- gives 1.
-
- 5.16.2.6: (expression)
- Surrounding an expression in brackets cause it to be evaluated
- first, just like in normal arithmetic e.g.
-
- 4 + 5 * 2
-
- gives 4 + 10 = 14 but
-
- (4 + 5) * 2
-
- gives 9 * 2 = 18. This also works with non-arithmetic operators
- e.g.
-
- NOT (A == B AND C == D)
-
- is different from
-
- (NOT A == B) AND (C == D)
-
- 5.16.2.7: LOAD
- LOAD will try to load an old game position recorded with SAVE and
- will return 1 if the load succeeded and 0 if it failed. The
- reason it returns a value is that it is commonly desired to do
- something like:
-
- IF LOAD
- PLAYER DESCRIBE_LOCATION
-
- i.e. only if the load succeeded, give the player an immediate
- reminder of where he was.
-
- LOAD will prompt the player for the name of the file which
- contains the saved game. It will fail if the file cannot be
- found. It will also fail if the file is not an OASYS saved game
- at all, since such files contain a special signature which OASYS
- can recognize. (This signature is different from the signature
- which identifies OASYS object files). However the LOAD function
- will not be able to detect if the saved game was saved from a
- different game. Such cases will produce "unpredictable results"
- and it will usually be necessary to reset the computer.
-
- What happens if you are testing a game, have saved a position,
- made a slight change to fix a bug and now want to reload that
- position to continue where you left off? You can do this *only
- if* you have not:
-
- added or removed any global variables
-
- added or removed any properties
-
- added or removed any classes
-
- added or removed any strings
-
- This last is the one that generally creates the most problems. A
- favourite trick when debugging a method when something is going
- wrong and you're not sure where is to insert statements at
- various points like PRINT "Reached point A", PRINT "Reached point
- B" etc. This will make your saved games unusable! A possible
- alternative is to put in statements like PRINT 1001, PRINT 1002
- etc. as substitutes.
-
- [The signature for an OASYS saved game is the 4-byte C string
- "oas\1" at the beginning of the file.]
-
- 5.16.2.8: RANDOM expression
- The expression must give an INT result, and RANDOM will return a
- random number between expression - 1 and 0 inclusive e.g.
-
- RANDOM 5
-
- will generate a random number somewhere between 0 and 4. RANDOM 1
- will always generate 0 and RANDOM 0 will give a "Division by
- zero" error message when the game is run.
-
- [The random number generator is seeded from the system clock at
- the start of every game so you should always get different
- results.]
-
- 5.16.2.9: CREATE class-name
- This will create an object of the given class and return a
- reference to it e.g.
-
- KITCHEN = CREATE ROOM
-
- Why is it said to return a "reference to" the object rather than
- just the object itself? Well suppose you have:
-
- OBJECT SWORD1
- OBJECT SWORD2
-
- SWORD1 = CREATE SWORD
- SWORD2 = SWORD1
-
- Then there is really only one sword but either of the two
- variables can be used to refer to it.
-
- SWORD1 DESC = "A large sword"
- PRINT SWORD2 DESC
-
- will give the result
-
- A large sword
-
- and
-
- DESTROY SWORD1
- PRINT SWORD2 DESC
-
- (here SWORD1 and SWORD2 must be *global* variables otherwise you
- must add
-
- SWORD1 = OBJECT 0
- SWORD2 = OBJECT 0
-
- - see the section on "DESTROY" for the reason.)
-
- will give
-
- *NULL STRING*
-
- because SWORD1 and SWORD2 were the same sword and if SWORD1 no
- longer exists then of course neither does SWORD2. On the other
- hand if you had originally put
-
- SWORD1 = CREATE SWORD
- SWORD2 = CREATE SWORD
-
- then you would indeed have two different swords.
-
- 5.16.2.10: THIS
- THIS will return a reference to the object to which the current
- method has been applied. See the earlier section on "THIS" for
- further details.
-
- 5.16.2.11: OBJECT expression
- All objects in the game at any given time are kept in a big list.
- OBJECT 1 returns a reference to the first object in this list,
- OBJECT 2 returns a reference to the second object in the list and
- so on. The expression can be anything that returns an INT value.
-
- Suppose there are currently 200 objects in the game. What happens
- if you try to evaluate OBJECT 201 or OBJECT -5? You get the "null
- object". This has been called OBJECT 0 throughout this manual
- because the expression OBJECT 0 does of course give the null
- object, but OBJECT -1 would do as well. So would OBJECT 20000
- unless your game is extremely large!
-
- One way to go through every object in the entire system is as
- follows:
-
- INT I
-
- I = 1
- DO
- {
- OBJECT I SOME_METHOD
- I = I + 1
- }
- WHILE OBJECT I EXISTS
-
- which will start at object 1 and apply SOME_METHOD to every
- object until it comes to the end of the list. However it's more
- efficient to use NEXT (q.v.).
-
- Objects are added to the end of the list when CREATEd and removed
- from the list when DESTROYed. The order of the list is always
- preserved even through a SAVE and LOAD. This means that if you
- have created object A before object B and you use the above
- technique (or the equivalent with NEXT) to go through the list,
- you will always come to A before B.
-
- Also if two or more objects are eligible for a command according
- to the selector methods the first one in the list is selected.
-
- This fact is used in ESCAPE.S in the boxes puzzle to ensure that
- the player is always trying to OPEN the correct box.
-
- 5.16.2.12: Local variable
- The name of a local variable is an expression returning the value
- of the variable.
-
- 5.16.2.13: Method argument
- Names of method arguments are equivalent to local variables.
-
- 5.16.2.14: Global variable
- The name of a global variable is an expression returning the
- value of the variable, unless there is a local variable or method
- argument with the same name (in which case the local variable or
- argument takes priority).
-
- 5.16.2.15: expression property-name
- The expression must be of type OBJECT. The value of the specified
- property of the object will be returned. If the expression is
- OBJECT 0 the value of the property will taken to be in accordance
- with the rules for uninitialized variables i.e. INT = 0, STRING =
- "*NULL STRING*" and OBJECT = OBJECT 0.
-
- 5.16.2.16: expression EXISTS
- The expression must give a value of type OBJECT. The EXISTS
- function will give a value of 1 if the object is not OBJECT 0 and
- 0 if the object is OBJECT 0.
-
- expression == OBJECT 0 would do the same job, but less
- efficiently.
-
- 5.16.2.17: expression IS class-name
- The expression must give a value of type OBJECT. A value of 1
- will be returned if the object's class is the indicated one, 0
- otherwise e.g. if you have previously done
-
- KITCHEN = CREATE ROOM
-
- then
-
- KITCHEN IS ROOM
-
- will return 1.
-
- 5.16.2.18: expression NEXT
- The expression must give a value of type OBJECT. The next object
- in the system object list will be returned (see "OBJECT
- expression" above). When applied to OBJECT 0 or when the object
- is the last in the list, OBJECT 0 is returned. NEXT can be used
- to go through all the objects in the game one by one e.g.
-
- OBJECT X
-
- X = OBJECT 1
- DO
- {
- X SOME_METHOD
- X = X NEXT
- }
- WHILE X EXISTS
-
- will apply SOME_METHOD to every object in existence.
-
- 5.16.2.19: expression method-name arguments
- A method which returns a value of INT, STRING or OBJECT can be
- called in an expression. The syntax is above where the first
- expression is an object to which the method is to be applied, and
- the list of arguments must match those expected by the method in
- number, position and type.
-
- The method must return a value, otherwise its call should be a
- statement and an attempt to use it in an expression will result
- in the error message "Void type used in expression".
-
- 5.17: Recursion
- (This section is not strictly necessary and may be skipped, but the
- material in it will make some kinds of things easier to do).
-
- Since everything must be defined before it is referred to, a method can
- only call other methods that have been defined before it. But a method
- can also call itself! This is called "recursion". For example, consider
- the definition of IS_VISIBLE that says "an object is visible if it is
- carried by the player, or in the player's location, or if it is inside
- another object which is both open and visible", e.g. something in a
- visible open box would also be visible. This can be defined as follows:
-
- METHOD INT IS_VISIBLE
- "That isn't here.\n"
- {
- IF THIS IN == PLAYER OR THIS IN == PLAYER IN
- RETURN 1
- IF NOT THIS IN EXISTS
- RETURN 0
- RETURN THIS IN IS_VISIBLE AND THIS IN IS_OPEN
- }
-
- [Since local variables are allocated on a stack, each re-entry of a
- method gets its own local variable space.]
-
- A recursive definition has been described as "something which is almost
- but not quite a circular definition". One pitfall of recursion is of
- accidentally leaving out the "almost but not quite" bit. For example,
- consider the following:
-
- METHOD INT IS_VISIBLE
- "That isn't here.\n"
- {
- RETURN THIS IN == PLAYER OR THIS IN == PLAYER IN OR
- (THIS IN IS_VISIBLE AND THIS IN IS_OPEN)
- }
-
- At first glance this looks equivalent to the earlier definition.
- However in this case, even if THIS IN == PLAYER IN and the object is in
- the player's current location, THIS IN IS_VISIBLE will still be called!
- (As explained in "Logical Operators", the OASYS OR is not very clever
- and always evaluates all of its arguments even when this should not be
- necessary). Similarly when THIS IN IS_VISIBLE is called, it will call
- METHOD IS_VISIBLE again and so on forever. Well it would be forever but
- OAI will quickly detect this situation when the game is being run and
- stop with the message "Stack overflow".
-
- Actually the above definition would work because sooner or later THIS
- would equal OBJECT 0 and METHOD IS_VISIBLE would return immediately
- without getting a chance to call THIS IN IS_VISIBLE. (Unless of course
- you had two objects each of which was inside the other!) But in general
- it's something to watch out for.
-
- 5.18: Errors
- The following sections list various kinds of error messages which the
- OAC and OAI programs may give and their causes. For both programs if
- you ever see an error message beginning with the words "Assertion
- failed", this probably indicates a bug in OASYS. In this case, please
- write down the error message and the circumstances under which it
- occurred and contact the author.
-
- 5.18.1: OAC
- Three kinds of error messages may be given by the OAC program:
-
- Normal error messages are indicated by
-
- Error near line xxx: yyy
-
- where xxx is the line number in the source file and yyy is a message
- indicating what the problem is.
-
- It frequently happens that OAC does not notice an error until a few
- lines after it has happened, so the real error may be a few lines
- before the indicated line number. However it will never be after the
- indicated line number.
-
- When one or more errors are encountered during translation, the
- translation will proceed as normal right up to the point where the
- object file has been produced. However, OAI will then delete the
- object file. The purpose of this is to make sure that an invalid
- object file is never produced, because then the computer could crash
- if an attempt was made to run it.
-
- Fatal error messages are indicated by
-
- Fatal error near line xxx: yyy
-
- The difference between normal and fatal errors is that when a fatal
- error is encountered, OAI doesn't understand what you are trying to
- do and is not able to continue translating your source code. Hence
- it will stop immediately and you must fix the problem before trying
- again. In this case no object file is created and if there was an
- old object file there it is not affected.
-
- Some errors occur when you have no INIT method defined, no strings,
- no vocabulary or whatever - the OAI program is designed on the
- assumption that these will be present and hence OAC checks for their
- presence. If you want to write a throwaway game to test some
- language feature, you could use a copy of EXAMPLE.S and modify it,
- as it already has all the minimum requirements to translate and run
- successfully.
-
- Other miscellaneous errors may also occur for events such as OAC
- being unable to find the source file. These normally involve
- translation stopping immediately as for a fatal error.
-
- 5.18.2: OAI
- The following error messages may be given by the OAI program when
- running an adventure:
-
- Not an OASYS file
-
- This message can only be given when OAI is trying to load the object
- file. It means that the file you have told it to load is not a valid
- object file produced by the OAC program.
-
- [The marker for an OASYS object file is the 4-byte C string "oas\0"
- at the beginning of the file.]
-
- Division by zero
-
- This means that somewhere in your method code an attempt has been
- made to divide by zero (trying to evaluate RANDOM 0 will have this
- effect).
-
- Stack overflow
-
- This means that your method code uses too much space in an area of
- memory known as the "stack". This is used as a scratchpad when
- evaluating expressions, and is also used for storing local variables
- and method arguments. A stack overflow will be produced by a runaway
- recursion (q.v.), otherwise you will have to reduce the depth of
- method calls, number of local variables etc. used by your code. Note
- that only the methods being called at the time of the stack overflow
- have any effect on the amount of stack space used.
-
- [A small amount of space on the hardware stack is used for each
- method call but local variables etc. are allocated on a separate
- software stack. It is an overflow of this stack which is indicated
- by the error message.]
-
- Nonexistent object
-
- An attempt has been made to DESTROY an object which is OBJECT 0.
-
- 6: Guidelines
- 6.1: Writing Adventures
- The following are guidelines on how to write adventure games with
- OASYS. You don't have to follow them but you may find them useful.
-
- OASYS is designed to be a relatively easy system with which to add
- stuff in as you go along: you can add new global variables, methods,
- properties etc. without too much difficulty. (Remember to delete your
- old saved positions!) However you should have at least a full map drawn
- before you sit down at the computer.
-
- Do things with objects rather than flags whenever possible, e.g. in
- ESCAPE there is a situation where the player encounters a tiger while
- walking along a corridor! In an older system this might have been
- handled by a global variable which recorded whether or not the tiger
- was still there or had been killed. In OASYS it was handled instead by
- a special object for the tiger - the object was DESTROYed when the
- tiger was defeated. This approach lets unusual events be handled easily
- (e.g. what happens if the player tries to GET TIGER?).
-
- Follow the "rule of trap the negatives". Suppose you have the player
- trying to blow something up with some explosive. You might start off
- with
-
- IF NOT EXPLOSIVE IS_CARRIED
- {
- PRINT "You have no explosive!\n"
- RETURN
- }
-
- and then
-
- IF NOT DETONATOR IS_CARRIED
- {
- PRINT "You've nothing to set off the explosive with!\n"
- RETURN
- }
-
- and after a few such checks, we now know that the player can indeed do
- the deed and we have:
-
- PRINT "Boom!\n"
- DESTROY X
- DESTROY EXPLOSIVE
- DESTROY DETONATOR
-
- or whatever. The point here is that you start off by trapping the most
- serious problem or "negative" (IF NOT...), with a message and a RETURN
- statement. If the method gets past that check you trap the next most
- serious problem and so on. Eventually if the method gets past all the
- checks you can assume everything is OK and proceed to carry out the
- command.
-
- Copy extensively from the example adventures and any others you can get
- your hands on. Don't reinvent the wheel!
-
- Follow the conventions for indenting statements within curly brackets,
- IF statements etc. as used in EXAMPLE.S and ESCAPE.S.
-
- If your word processor allows it, set tab stops to only three spaces
- apart so when there are several levels of indentation in your code you
- still have a good deal of room left before you hit the right-hand edge
- of the screen.
-
- Back up your source code onto floppy disk, magnetic tape or other
- medium after every working session. (It is not necessary to back up the
- object file since you can easily recreate it from the source file).
-
- When you get a "bug" or error in your game, which you inevitably will,
- use the strategy of "divide and conquer" i.e. put in diagnostic PRINT
- statements to tell you the values of variables etc. at various points
- in the code. Finding whereabouts things are going wrong is half the
- battle. (Remember if you put in any PRINT statements with strings,
- delete your old saved positions!)
-
- Put a comment such as //DEBUG beside all the diagnostic statements so
- when you do find the bug you can use your word processor's search
- facility to find and remove the diagnostics.
-
- Put comments beside any obscure bits of code you write. Even if nobody
- else ever reads your code you might want to come back to it in a
- month's time.
-
- Before starting to write the Adventure Game To End All Adventure Games,
- ask yourself will it fit into your computer's memory? There's no point
- in spending weeks writing half of a huge masterpiece and then having to
- abandon it because you've run out of memory. Compare your design with
- the ESCAPE game to get an estimate of relative sizes.
-
- 6.2: Efficiency
- The following are guidelines on how to make your game more efficient in
- two ways: to use less memory in the computer when running and to run
- faster. However where efficiency conflicts with ease of writing and
- debugging the adventure, efficiency usually gets lower priority. An
- exception sometimes happens when the adventure becomes so big it won't
- fit into the computer's memory!
-
- You can reduce memory usage by either reducing the size of the object
- file (which must be loaded into memory when the game is run) or by
- reducing the number of objects created or the number of object
- properties or global variables (which also take up memory).
-
- See your computer's manual for information on how to check the size of
- the object file, and the amount of free memory available.
-
- Since you can't reduce the number of objects without making your game
- less complex, this is not recommended.
-
- Eliminating an object property will reduce memory usage. Eliminating a
- global variable will not significantly reduce memory usage - properties
- take up memory for each and every object created, whereas global
- variables only take up memory once. Local variables do not take up
- memory in the normal sense, but they take up space on the "stack" (see
- the "Errors" section) which is also of limited size.
-
- Most of the space in the object file is taken up by actual method code
- and strings to be displayed to the player - these tend to be of roughly
- equal size. The extra "overhead" information takes up relatively little
- space.
-
- You can reduce the size of the object file by reducing the number and
- length of your strings. This is a measure of desperation, since it will
- reduce the quality of your adventure. Where possible, reducing the size
- of the method code while still having it do the same job is the best
- course.
-
- In the current version of OASYS, it doesn't matter whether two strings
- are the same or different - each one is still stored separately.
- However this may change in later versions, so if you have the string
- "Fred" several times in your code, it would only take up space once.
- (This also affects comparison of strings - see "Comparison Operators"
- for further details).
-
- The use of comments and the insertion of extra spaces and blank lines
- in your source code makes no difference whatsoever to the efficiency of
- the adventure (though they will make the souce code take up more disk
- space and take slightly longer to translate into object code), because
- comments etc. are not included in the object file.
-
- In general, the use of longer identifiers for things like method and
- variable names also makes no difference to efficiency for the above
- reason. However due to a quirk of the OAC program design, names of
- method arguments are included in the object file. So using shorter
- names for method arguments will save a small amount of space, as will
- using the same argument names in different methods - if you have many
- methods with arguments called FRED, the word FRED is only stored once.
- (In future versions, method argument names may not be included in the
- object file).
-
- Using THIS is more efficient than using a variable or method argument.
- Suppose you have a method in which you know beforehand that THIS will
- be the same as PLAYER. (For example, verb methods in a game which does
- not allow the player to talk to other objects). Then using THIS instead
- of PLAYER will make your adventure more efficient.
-
- Using IS is more efficient than comparing variables. For example,
- suppose you have IF THIS == PLAYER in a method. If you have a CLASS
- PLAYER of which the global variable PLAYER is the only example, you
- could instead write IF THIS IS PLAYER.
-
- When a WHILE loop and a DO...WHILE loop will do the same job (e.g.
- because you know the condition will always be true the first time round
- the loop), DO...WHILE is more efficient.
-
- 7: Versions
- The version number of each OASYS program is displayed in a one-line
- message when the program is run. (It should be the same for all versions).
-
- This copy of the manual is for version 1.0.
-
- It is quite likely that an object file produced with one version of OAC
- will not run with a later version of OAI. In this case OAI will give the
- message "Not an OASYS file" when it fails to recognize the object file.
-
- It is less likely but still possible that a source file written for one
- version of OAC will not be accepted by a later version of OAC, in other
- words that the definition of the language will change.
-
- This means that if you get a later version of OASYS you shouldn't throw
- away your old version until you've checked whether or not the later
- version accepts your old code.
-